package com.ioe.p2p;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

public class P2PClient {
    // 162.14.67.52
    // localhost
    private static final InetSocketAddress server = new InetSocketAddress("162.14.67.52", 8000);

    private static final Map<String, String> userTable = new HashMap<>();

    private static String localName;

    private static String objName;

    private static final Object userLock = new Object();

    private static final Object connectLock = new Object();

    private static boolean connected = false;

    private static DatagramSocket ds;

    public static void main(String[] args) throws Exception {
            ds = new DatagramSocket();
            // 用户登记
            Scanner sc = new Scanner(System.in);
            System.out.println("用户名称：");
            localName = sc.nextLine();
            ds.send(new DatagramPacket(
                    localName.getBytes(),
                    localName.length(),
                    server
            ));
            byte[] bytes = new byte[1024 * 64];
            DatagramPacket dpp = new DatagramPacket(bytes, bytes.length);
            updateUser(dpp);
            // 打洞建立连接
            // 0 -> src - 1 -> obj - 2 -> content
            for (;;){
                if (connected){
                    break;
                }
                Scanner scanner = new Scanner(System.in);
                userTable.keySet().forEach(System.out::println);
                System.out.println("选择发送目标（输入：exit，刷新用户表）：");
                String line = scanner.nextLine();
                if (line.equals("exit")){
                    continue;
                }
                if (connected){
                    continue;
                }else {
                    objName = line;
                }
                System.out.println("选择连接目标名称：" + objName);
                String objAddress = getObjAddress();
                if (objAddress == null) continue;
                System.out.println("选择目标地址：" + objAddress);
                String[] od = objAddress.split(";");
                try {
                    // 发送前通知服务器我要发送消息给某人
                    String notified = localName + "-" + objName + "-" + ":1:";
                    DatagramPacket packet = new DatagramPacket(
                            notified.getBytes(StandardCharsets.UTF_8),
                            notified.length(),
                            server
                    );
                    ds.send(packet);
                    // 断开与服务器连接，发送消息给目标
                    DatagramPacket newDpp = new DatagramPacket(new byte[1024 * 64], new byte[1024 * 64].length);
                    updateUser(newDpp);
                    String res = notified.replace(":1:", ":2:");
                    DatagramPacket newPacket = new DatagramPacket(
                            res.getBytes(StandardCharsets.UTF_8),
                            res.length(),
                            new InetSocketAddress(od[1], Integer.parseInt(od[2]))
                    );
                    ds.send(newPacket);
                    break;
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("连接中。。。");
            Thread.sleep(3000);
            for (;;){
                // 0 -> src - 1 -> obj - 2 -> content
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入发送内容：");
                String content = scanner.nextLine();
                String sendMsg = localName + "-" + objName + "-" + content;
                String objAddress = getObjAddress();
                if (objAddress == null) continue;
                String[] od = objAddress.split(";");
                try {
                    ds.send(new DatagramPacket(
                            sendMsg.getBytes(StandardCharsets.UTF_8), sendMsg.length(),
                            new InetSocketAddress(od[1], Integer.parseInt(od[2]))
                    ));
                    if (!connected){
                        System.out.println("连接失败");
                    }
                } catch (IOException e) {
                    System.out.println("发送消息失败");
                    break;
                }
            }
    }

    private static String getObjAddress() {
        List<String> collect = userTable.keySet().stream().filter(s -> s.contains(objName)).collect(Collectors.toList());
        if (collect.isEmpty()){
            System.out.println("该目标没在线");
            return null;
        }
        return userTable.get(collect.get(0));
    }

    private static void updateUser(DatagramPacket dpp) {
        // 开启线程接受消息和更新用户
        new Thread(() -> {
            for(;;){
                try {
                    ds.receive(dpp);
                } catch (IOException e) {
                    System.out.println("中断接受消息");
                    break;
                }
                byte[] bytes = new byte[dpp.getLength()];
                byte[] data = dpp.getData();
                System.arraycopy(data, 0, bytes, 0, bytes.length);
                String text = new String(bytes, StandardCharsets.UTF_8);
                if (text.contains(":0:")){
                    // 接受现在有多少人在线
                    // 更新在线用户人数
                    synchronized (userLock){
                        List<String> collect = Arrays.stream(text.replace(":0:", "").split("-")).filter(
                                s -> !s.contains(localName)
                        ).collect(Collectors.toList());
                        collect.forEach(s -> userTable.put(s.split(";")[0], s));
                    }
                    continue;
                }
                if (text.contains(":1:")){
                    byte[] b = new byte[1024 * 64];
                    DatagramPacket newDpp = new DatagramPacket(b, b.length);
                    updateUser(newDpp);
                    String sendMsg = text.replace(":1:", ":2:");
                    objName = sendMsg.split("-")[0];
                    String objAddress = getObjAddress();
                    if (objAddress == null) continue;
                    String[] od = objAddress.split(";");
                    // 断开与服务器连接，发送消息给目标
                    try {
                        ds.send(new DatagramPacket(
                                sendMsg.getBytes(StandardCharsets.UTF_8),
                                sendMsg.length(),
                                new InetSocketAddress(od[1], Integer.parseInt(od[2]))
                        ));
                        break;
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                if (text.contains(":2:")){
                    if (connected){
                        continue;
                    }
                    synchronized (connectLock){
                        connected = true;
                        System.out.println(localName + "->" + objName + "：成功连接！");
                    }
                    continue;
                }
                synchronized (connectLock){
                    connected = true;
                    System.out.println(localName + "->" + objName + "：成功连接！");
                }
                String[] msg = text.split("-");
                System.out.println(msg[1] + "收到" + msg[0] + "消息：" + msg[2]);
            }
        }).start();
    }
}